# jsb/eventbase.py
#
#

""" base class of all events.  """

## jsb imports

from channelbase import ChannelBase
from jsb.utils.lazydict import LazyDict
from jsb.utils.generic import splittxt, stripped, waitforqueue
from errors import NoSuchUser
from jsb.utils.opts import makeeventopts
from jsb.utils.trace import whichmodule
from jsb.utils.exception import handle_exception
from jsb.utils.locking import lockdec
from jsb.lib.config import Config, getmainconfig
from jsb.lib.users import getusers

## basic imports

from collections import deque
from xml.sax.saxutils import unescape
import copy
import logging
import Queue
import types
import socket
import threading
import time
import thread
import urllib

## defines

cpy = copy.deepcopy
lock = thread.allocate_lock()
locked = lockdec(lock)

## classes

class EventBase(LazyDict):

    """ basic event class. """

    def __init__(self, input={}, bot=None):
        LazyDict.__init__(self)
        self.options = None
        self.oldtxt = ""
        self.userhost = ""
        self.txt = ""
        self.usercmnd = ""
        self.bottype = "botbase"
        self.threads = []
        self.relayed = []
        self.path = []
        self.cbs = []
        self.stop = False
        self.bonded = False
        self.ctime = time.time()
        self.speed = 5
        self.nrout = 0
        self.pipelined = False
        self.dontbind = False
        self.doregister = False
        self.inchan = False
        self.isoutput = False
        if input: self.copyin(input)
        self.clear()
        self.auth = self.auth or self.userhost
        if bot: self.bot = bot
        where = whichmodule(3)
        logging.debug("created from %s" % where)

    def fresh(self):
        return EventBase()

    def clear(self, *args):
        self.ok = self.ok or threading.Event()
        self.waiting = []
        self.busy = deque()
        self.inqueue = deque()
        self.outqueue = deque()
        self.resqueue = deque()
        self.prev = None
        return self

    def setup(self, a):
        self.waiting = []
        self.ok = a.ok or threading.Event()
        self.busy = a.busy or deque()
        self.inqueue = a.inqueue or deque()
        self.outqueue = a.outqueue or deque()
        self.resqueue = a.resqueue or deque()
        self.prev = a.prev or None
        return self

    def __deepcopy__(self, a):
        """ deepcopy an event. """
        logging.info("copy - %s" % whichmodule(4))
        e = EventBase(self)
        #e.clear()
        return e

    def ready(self, what=None):
        """ signal the event as ready - push None to all queues. """
        if self.pipelined: return
        logging.info("busy are %s" % str(self.busy))
        try: self.busy.remove(what or str(self))
        except ValueError: pass
        if not self.busy or self.busy and self.busy[-1] == "START":
            logging.info("%s is ready" % str(self))
            self.ok.set()

    def execwait(self, direct=False):
        from jsb.lib.commands import cmnds
        e = cpy(self)
        #e = self
        if direct:
            e.direct = True
            if cmnds.woulddispatch(self.bot, e):
                ee = cmnds.dispatch(self.bot, e)
                if ee: return ee.wait()
        else:
            e = self.execute()
            if e: e.wait() ; return
        logging.warn("%s wont dispatch" % self.txt)

    def wait(self, nr=60):
        count = int(nr)
        result = []
        logging.info("waiting for %s - from %s" % (str(self.busy), whichmodule()))
        self.ok.wait(nr/10.0)
        if self.busy: logging.info("busy is %s" % str(self.busy))
        #result = list(self.resqueue)
        return self.resqueue

    def execute(self, *args, **kwargs):
        """ dispatch event onto the cmnds object. check for pipelines first. """
        logging.warn("execute %s" % self.cbtype)
        from jsb.lib.commands import cmnds
        if not self.pipelined and ' ! ' in self.txt: res = self.dopipe(*args, **kwargs)
        else: res = cmnds.dispatch(self.bot, self, *args, **kwargs)
        return res
                
    def dopipe(self, *args, **kwargs):
        """ split cmnds, create events for them, chain the queues and dispatch.  """
        from jsb.lib.commands import cmnds
        origout = self.outqueue
        events = []
        splitted = self.txt.split(" ! ")
        self.pipelined = True
        for i in range(len(splitted)):
            e = cpy(self)
            e.clear()
            e.txt = splitted[i].strip()
            e.usercmnd = e.txt.split()[0].lower()
            e.makeargs()
            events.append(e)
        prev = None
        for i in range(len(events)):
            e = events[i]
            if prev: e.inqueue = prev.outqueue ; e.prev = prev
            prev = e
        events[-1].pipelined = False
        events[-1].dontclose = False
        for i in range(len(events)):
            e = events[i]
            logging.debug('created event for pipeline: %s' % e.tojson())
            e.execute()
            #self.bot.put(e)
        return events[-1]

    def prepare(self, bot=None):
        """ prepare the event for dispatch. """
        if bot: self.bot = bot
        assert(self.bot)
        self.origin = self.channel
        self.origtxt = self.txt
        self.makeargs()
        if self.txt: self.usercmnd = self.txt.split()[0]
        logging.debug("%s - prepared event - %s" % (self.auth, self.cbtype))

    def bind(self, bot=None, user=None, chan=None):
        """ bind event.bot event.user and event.chan to execute a command on it. """
        if self.dontbind: logging.warn("dontbind is set on event . .not binding"); return
        if self.bonded and (bot and not bot.isgae): logging.debug("already bonded") ; return
        self.auth = self.auth or self.userhost
        target = self.auth
        bot = bot or self.bot
        if not self.chan:
            if chan: self.chan = chan
            elif self.channel: self.chan = ChannelBase(self.channel, bot.cfg.name)
            elif self.userhost: self.chan = ChannelBase(self.userhost, bot.cfg.name)
            if self.chan:
                self.debug = self.chan.data.debug or False
                logging.debug("channel bonded - %s" % self.chan.data.tojson())
        if not target: self.prepare(bot) ; self.bonded = True ; return
        if not self.user and target:
            if user: u = user
            else: u = bot.users.getuser(target)
            if not u: 
                cfg = getmainconfig()
                if cfg.auto_register and self.iscmnd():
                    u = bot.users.addguest(target)
                    if u: logging.warn("auto_register applied")
                    else: logging.warn("can't add %s to users database" % target)
            if u: logging.info("u is %s" % str(u.data))
            self.user = u or logging.info("can't find user for %s" % target)
            if self.user: logging.debug("user bonded from %s" % whichmodule())
            else: logging.info("can't find %s user" % target)
        if not self.user and target: logging.info("no %s user found .. setting nodispatch" % target) ; self.nodispatch = True
        self.prepare(bot)
        if self.bot: self.inchan = self.channel in self.bot.state.data.joinedchannels
        self.bonded = True
        return self

    def addwaiting(self, event):
        if not event in self.waiting: self.waiting.append(event)

    def parse(self, event, *args, **kwargs):
        """ overload this. """
        self.bot = event.bot
        self.origin = event.origin
        self.ruserhost = self.origin
        self.userhost = self.origin
        self.channel = event.channel
        self.auth = stripped(self.userhost)

    def copyin(self, eventin):
        """ copy in an event. """
        self.update(eventin)
        return self

    def reply(self, txt, result=[], event=None, origin="", dot=u", ", nr=375, extend=0, showall=False, *args, **kwargs):
        """ reply to this event """
        try: target = self.channel or self.arguments[1]
        except (IndexError, TypeError): target = self.channel or "nochannel"
        if self.silent:
            self.msg = True
            self.bot.say(self.nick, txt, result, self.userhost, extend=extend, event=self, dot=dot, nr=nr, showall=showall, *args, **kwargs)
        elif self.isdcc: self.bot.say(self.sock, txt, result, self.userhost, extend=extend, event=self, dot=dot, nr=nr, showall=showall, *args, **kwargs)
        else: self.bot.say(target, txt, result, self.userhost, extend=extend, event=self, dot=dot, nr=nr, showall=showall, *args, **kwargs)
        return self

    def missing(self, txt):
        """ display missing arguments. """
        self.reply("%s %s" % (self.usercmnd, txt), event=self) 
        return self

    def done(self):
        """ tell the user we are done. """
        self.reply('<b>done</b> - %s' % self.txt, event=self)
        return self

    def leave(self):
        """ lower the time to leave. """
        self.ttl -= 1
        if self.ttl <= 0 : self.status = "done"
        logging.info("======== STOP handling event ========")

    def makeoptions(self):
        """ check the given txt for options. """
        try: self.options = makeeventopts(self.txt)
        except: handle_exception() ; return 
        if not self.options: return
        if self.options.channel: self.target = self.options.channel
        logging.debug("options - %s" % unicode(self.options))
        self.txt = ' '.join(self.options.args)
        self.makeargs()

    def makeargs(self):
        """ make arguments and rest attributes from self.txt. """
        if not self.txt:
            self.args = []
            self.rest = ""
        else:
            args = self.txt.split()
            self.chantag = args[0]
            if len(args) > 1:
                self.args = args[1:]
                self.rest = ' '.join(self.args)
            else:
                self.args = []
                self.rest = ""

    def makeresponse(self, txt, result, dot=u", ", *args, **kwargs):
        """ create a response from a string and result list. """
        return self.bot.makeresponse(txt, result, dot, *args, **kwargs)

    def less(self, what, nr=365):
        """ split up in parts of <nr> chars overflowing on word boundaries. """
        return self.bot.less(what, nr)

    def isremote(self):
        """ check whether the event is off remote origin. """
        return self.txt.startswith('{"') or self.txt.startswith("{&")

    def iscmnd(self):
        """ check if event is a command. """
        if not self.txt: logging.debug("no txt set.") ; return
        if self.iscommand: return self.txt
        if self.isremote(): logging.info("event is remote") ; return
        logging.debug("trying to match %s" % self.txt)
        cc = "!"
        if not self.chan: logging.warn("channel is not set.") ; return False
        cc = self.chan.data.cc
        if not cc: self.chan.data.cc = "!" ; self.chan.save()
        if not cc: cc = "!"
        if self.type == "DISPATCH": cc += "!"
        if not self.bot: logging.warn("bot is not bind into event.") ; return False
        logging.debug("cc for %s is %s (%s)" % (self.channel or self.userhost, cc, self.bot.cfg.nick))
        if self.txt[0] in cc: return self.txt[1:]
        matchnick = unicode(self.bot.cfg.nick + u":")
        if self.txt.startswith(matchnick): return self.txt[len(matchnick):]
        return False

    def stripcc(self):
        bla = self.iscmnd()
        if bla: return bla
        return self.txt
   